'use strict';
/*******
 * @description: 获取微信公众号accessToken类
 * @author: 琴时
 * @param {*}
 * @return {accessToken}
 */
/* 
1. 特点：
    - 唯一性
    - 有效期为2小时，提前3分钟请求
    - 接口权限 每天限制2000次请求
2. 请求地址
    https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}
3. 请求方式
    GET
4. 整理思路：
    读取全局缓存数据(readAccessToken)
        - 有数据
          - 判断是否过期(isValid)
            - 已过期
              - 重新请求获取access_token(getAccessToken)
              - 全局保存覆盖之前的数据（确保唯一性）(saveAccessToken)
            - 没有过期
              - 直接使用
        - 无数据
          - 发送请求获取access_token(getAccessToken)
          - 全局保存覆盖之前的数据（确保唯一性）(saveAccessToken)
*/
const axios = require('axios')

class AccessToken {
  constructor(accessToken, expireTime) {
    this.accessToken = accessToken
    this.expireTime = expireTime
  }

  /*
   * 检查AccessToken是否有效，检查规则为当前时间和过期时间进行对比
   */
  isValid() {
    return !!this.accessToken && Date.now() < this.expireTime
  }
}
class API {
  constructor(appId, appSecret, getToken, saveToken, tokenFromCustom) {
    this.appId = appId
    this.appSecret = appSecret
    this.getToken = getToken || async function () {
      return this.store
    }
    this.saveToken = saveToken || async function (token) {
      this.store = token
      if (process.env.NODE_ENV === 'production') {
        console.warn('Don\'t save token in memory, when cluster or multi-computer!');
      }
    }
    this.prefix = 'https://api.weixin.qq.com/cgi-bin/'
    this.snsPrefix = 'https://api.weixin.qq.com/sns/'
    this.mpPrefix = 'https://mp.weixin.qq.com/cgi-bin/'
    this.fileServerPrefix = 'http://file.api.weixin.qq.com/cgi-bin/'
    this.payPrefix = 'https://api.weixin.qq.com/pay/'
    this.merchantPrefix = 'https://api.weixin.qq.com/merchant/'
    this.customservicePrefix = 'https://api.weixin.qq.com/customservice/'
    this.wxaPrefix = 'https://api.weixin.qq.com/wxa/'
    this.defaults = {}
    this.tokenFromCustom = tokenFromCustom
    this.ensureAccessToken()
  }
  /**
   * 获取access_token
   */
  async getAccessToken() {
    const params = {
      grant_type: 'client_credential',
      appid: this.appId,
      secret: this.appSecret,
    }
    /* {"access_token": "ACCESS_TOKEN","expires_in": 7200} */
    const { data: res } = await axios.get(`${this.prefix}token`, { params })
    if (res && res.errcode) {
      console.log('getAccessToken请求异常:', res)
      return res
    }
    /* 自定义设置过期时间:提前3分钟 */
    res.expires_in = Date.now() + (res.expires_in - 180) * 1000
    const token = new AccessToken(res.access_token, res.expires_in)
    await this.saveToken(token)
    return token
  }

  /*
   * 应用开发者无需直接调用此API。
   */
  async ensureAccessToken() {
    // 调用用户传入的获取token的异步方法，获得token之后使用（并缓存它）。
    const token = await this.getToken()
    let accessToken;
    if (
      token && (accessToken = new AccessToken(token.accessToken, token.expireTime)).isValid()
    ) {
      return accessToken
    } else if (this.tokenFromCustom) {
      let err = new Error('accessToken Error')
      err.name = 'WeChatAPIError'
      err.code = 40001
      throw err
    }
    return this.getAccessToken()
  }
}

/**
 * 用于支持对象合并。将对象合并到API.prototype上，使得能够支持扩展
 * Examples:
 * ```
 * API.mixin(require(''));
 * ```
 * @param {Object} obj 要合并的对象
 */
 API.mixin = function (obj) {
  for (var key in obj) {
    if (API.prototype.hasOwnProperty(key)) {
      throw new Error('Don\'t allow override existed prototype method. method: '+ key);
    }
    API.prototype[key] = obj[key];
  }
};

API.AccessToken = AccessToken;

module.exports = API
